pub mod req;
pub mod rsp;

use crate::core::entity::jump_url;
use crate::core::entity::native_version_pair;
use crate::core::entity::native_version_pair::NativeVersionPair;
use crate::core::entity::resource_url;
use crate::core::entity::whitelist::Whitelist;
use crate::core::model::channel_setting::ChannelSetting;
use crate::core::model::native_version_setting::NativeVersionSetting;
use crate::custom_filter::{ChannelSettings, CustomFilter, HotUpdateSettings, WhitelistSettings};
use crate::error::{IntoWithStatus, ToStatusCode};
use crate::nested_map::NestedMap;
use crate::router::channel_setting_router::req::{
    PostChannelSettingRequest, PutJumpUrlRequest, PutNativeVersionRequest,
};
use crate::router::channel_setting_router::rsp::GetChannelInfoResponse;
use crate::router::dto::hot_update_info::HotUpdateInfo;
use share::core::channel::Channel;
use share::core::resource_version;
use snafu::{OptionExt, ResultExt};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use warp::http::StatusCode;
use warp::{Filter, Rejection, Reply};

#[derive(Debug, snafu::Snafu)]
pub enum Error {
    #[snafu(display("Failed to get channel setting by channel: {channel:?}"))]
    NotFound { channel: Channel },
    #[snafu(display("Invalid native version. -> {source}"))]
    NativeVersion { source: native_version_pair::Error },
    #[snafu(display("Invalid resource version. -> {source}"))]
    ResourceVersion { source: resource_version::Error },
    #[snafu(display("Invalid resource url. -> {source}"))]
    ResourceUrl { source: resource_url::Error },
    #[snafu(display("Invalid jump url. -> {source}"))]
    JumpUrl { source: jump_url::Error },
}

impl ToStatusCode for Error {
    fn status_code(&self) -> StatusCode {
        match self {
            Error::NotFound { .. } => StatusCode::NOT_FOUND,
            Error::NativeVersion { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Error::ResourceVersion { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Error::ResourceUrl { .. } => StatusCode::INTERNAL_SERVER_ERROR,
            Error::JumpUrl { .. } => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

pub struct Implementation {}

impl Implementation {
    async fn post(
        req: PostChannelSettingRequest,
        channel_settings: Arc<RwLock<ChannelSettings>>,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let native_version_setting =
            NativeVersionPair::new(req.min_native_version, req.latest_native_version)
                .context(NativeVersionSnafu {})?;

        whitelist_settings
            .insert(
                &req.channel,
                &native_version_setting.latest(),
                Whitelist::new(),
            )
            .await
            .ok()
            .context(NotFoundSnafu {
                channel: req.channel,
            })?;

        hot_update_settings
            .insert(
                &req.channel,
                &native_version_setting.latest(),
                NativeVersionSetting {
                    resource_version: req.resource_version,
                    resource_url: req.resource_url,
                },
            )
            .await
            .ok()
            .context(NotFoundSnafu {
                channel: req.channel,
            })?;

        let mut lock = channel_settings.write().await;
        let channel_setting = ChannelSetting {
            name: req.channel,
            native_version: native_version_setting,
            jump_url: req.jump_url,
        };

        lock.insert(req.channel, channel_setting);

        Ok(())
    }

    async fn delete(
        channel: Channel,
        channel_settings: Arc<RwLock<ChannelSettings>>,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let _ = whitelist_settings.delete_by_channel(&channel).await;
        let _ = hot_update_settings.delete_by_channel(&channel).await;

        let mut lock = channel_settings.write().await;
        lock.remove(&channel);

        Ok(())
    }

    async fn put_native_version(
        req: PutNativeVersionRequest,
        channel_settings: Arc<RwLock<ChannelSettings>>,
    ) -> Result<()> {
        let native_version_setting =
            NativeVersionPair::new(req.min_native_version, req.latest_native_version)
                .context(NativeVersionSnafu {})?;

        let mut lock = channel_settings.write().await;
        let channel_setting = lock.get_mut(&req.channel).context(NotFoundSnafu {
            channel: req.channel,
        })?;

        channel_setting.native_version = native_version_setting;

        Ok(())
    }

    async fn put_jump_url(
        req: PutJumpUrlRequest,
        channel_settings: Arc<RwLock<ChannelSettings>>,
    ) -> Result<()> {
        let mut lock = channel_settings.write().await;
        let channel_setting = lock.get_mut(&req.channel).context(NotFoundSnafu {
            channel: req.channel,
        })?;

        channel_setting.jump_url = req.jump_url;

        Ok(())
    }

    async fn get_channel_info(
        channel: Channel,
        channel_settings: Arc<RwLock<ChannelSettings>>,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<GetChannelInfoResponse> {
        let hot_update_settings = hot_update_settings
            .get_by_channel(&channel)
            .await
            .ok()
            .context(NotFoundSnafu { channel })?;

        let whitelist_settings = whitelist_settings
            .get_by_channel(&channel)
            .await
            .ok()
            .context(NotFoundSnafu { channel })?;

        let mut hot_update_infos = HashMap::new();
        {
            let l = hot_update_settings.read().await;
            let l1 = whitelist_settings.read().await;

            for (v, h) in l.iter() {
                let whitelist = l1
                    .get(&v)
                    .map(|w| w.get_all())
                    .unwrap_or(HashMap::default());

                hot_update_infos.insert(
                    v.clone(),
                    HotUpdateInfo {
                        resource_version: h.resource_version.clone(),
                        resource_url: h.resource_url.clone(),
                        whitelist,
                    },
                );
            }
        }

        let lock = channel_settings.read().await;
        let channel_setting = lock.get(&channel).context(NotFoundSnafu { channel })?;

        let response = GetChannelInfoResponse {
            name: channel_setting.name,
            latest_native_version: channel_setting.native_version.latest(),
            min_native_version: channel_setting.native_version.min(),
            jump_url: channel_setting.jump_url.clone(),
            hot_update_settings: hot_update_infos,
        };

        Ok(response)
    }
}

struct Router {}

impl Router {
    fn post(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.channel_settings())
            .and(filter.hot_update_settings())
            .and(filter.whitelist_settings())
            .then(
                |req, channel_setting, hot_update_setting, whitelist| async move {
                    Implementation::post(req, channel_setting, hot_update_setting, whitelist)
                        .await
                        .into_with_status()
                },
            )
    }

    fn delete(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::param())
            .and(filter.channel_settings())
            .and(filter.hot_update_settings())
            .and(filter.whitelist_settings())
            .then(
                |channel, channel_setting, hot_update_setting, whitelist| async move {
                    Implementation::delete(channel, channel_setting, hot_update_setting, whitelist)
                        .await
                        .into_with_status()
                },
            )
    }

    fn put_native_version(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::put()
            .and(warp::path("native_version"))
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.channel_settings())
            .then(|req, channel_setting| async move {
                Implementation::put_native_version(req, channel_setting)
                    .await
                    .into_with_status()
            })
    }

    fn put_jump_url(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::put()
            .and(warp::path("jump_url"))
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.channel_settings())
            .then(|req, channel_settings| async move {
                Implementation::put_jump_url(req, channel_settings)
                    .await
                    .into_with_status()
            })
    }

    fn get_channel_info(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path("info"))
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.channel_settings())
            .and(filter.hot_update_settings())
            .and(filter.whitelist_settings())
            .then(
                |channel, channel_setting, hot_update_setting, whitelist| async move {
                    Implementation::get_channel_info(
                        channel,
                        channel_setting,
                        hot_update_setting,
                        whitelist,
                    )
                    .await
                    .into_with_status()
                },
            )
    }
}

pub fn get_all_router(
    filter: Arc<CustomFilter>,
) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path::path("channel_setting").and(
        Router::post(filter.clone())
            .or(Router::delete(filter.clone()))
            .or(Router::put_native_version(filter.clone()))
            .or(Router::put_jump_url(filter.clone()))
            .or(Router::get_channel_info(filter.clone())),
    )
}
